From a892d9eb500555b259e51dba6d0f6a7db1ea8f33 Mon Sep 17 00:00:00 2001 From: Bastian Germann Date: Sun, 11 Oct 2026 23:59:36 +0200 Subject: [PATCH] Fix non-reproducible TrueType hinting: invalid qsort comparators Several comparison functions passed to qsort() violated the ordering axioms. With glibc >= 2.37 qsort() takes an in-place introsort path that has undefined behaviour for such comparators and reads uninitialised memory, so the generated TrueType instructions varied between builds (e.g. FreeSerif.ttf from the GNU FreeFont sources). Make SortBlues(), SortStems(), sortbynum(), sortedges(), stem_cmp() and line_pt_cmp() return a proper trichotomous result and compare both arguments symmetrically. Gbp-Pq: Name 0004-qsort-comparators.patch --- fontforge/nowakowskittfinstr.c | 11 +++++++---- fontforge/stemdb.c | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/fontforge/nowakowskittfinstr.c b/fontforge/nowakowskittfinstr.c index fce8dc5..c6592eb 100644 --- a/fontforge/nowakowskittfinstr.c +++ b/fontforge/nowakowskittfinstr.c @@ -467,7 +467,8 @@ return !((b2 > o1) || (o2 < b1)); /* To be used with qsort() - sorts BlueZone array by base in ascending order. */ static int SortBlues(const void *a, const void *b) { - return ((BlueZone *)a)->base > ((BlueZone *)b)->base; + real ba = ((BlueZone *)a)->base, bb = ((BlueZone *)b)->base; + return (ba > bb) - (ba < bb); } /* Import blue data into global instructing context. Include family blues too. @@ -617,7 +618,8 @@ static void GICImportBlues(GlobalInstrCt *gic) { /* To be used with qsort() - sorts StdStem array by width in ascending order. */ static int SortStems(const void *a, const void *b) { - return ((StdStem *)a)->width > ((StdStem *)b)->width; + real wa = ((StdStem *)a)->width, wb = ((StdStem *)b)->width; + return (wa > wb) - (wa < wb); } /* Import stem data into global instructing context. We deal only with @@ -2596,7 +2598,8 @@ static void init_edge(InstrCt *ct, real base, int contour_direction) { /* To be used with qsort() - sorts integer array in ascending order. */ static int sortbynum(const void *a, const void *b) { - return *(int *)a > *(int *)b; + int ia = *(const int *)a, ib = *(const int *)b; + return (ia > ib) - (ia < ib); } /* Find element's index within an array - return -1 if element not found. */ @@ -4916,7 +4919,7 @@ struct stemedge { /* To be used with qsort() - sorts edge array in ascending order. */ static int sortedges(const void *_e1, const void *_e2) { const struct stemedge *e1 = _e1, *e2 = _e2; - return ( e1->pos > e2->pos ); + return ( e1->pos > e2->pos ) - ( e1->pos < e2->pos ); } static int AddEdge(InstrCt *ct, StemData *stem, int is_l, struct stemedge *edgelist, int cnt) { diff --git a/fontforge/stemdb.c b/fontforge/stemdb.c index 0dc1750..f412e8f 100644 --- a/fontforge/stemdb.c +++ b/fontforge/stemdb.c @@ -447,12 +447,22 @@ static int line_pt_cmp( const void *_p1, const void *_p2 ) { struct linedata *line; double ppos1=0,ppos2=0; + /* Pick a line shared by both points, considering both arguments so + * that the comparison is symmetric: with an asymmetric comparator + * qsort() has undefined behaviour and the resulting ordering depends + * on uninitialised memory (non-reproducible output). */ if ( (*pd1)->prevline != NULL && ( (*pd1)->prevline == (*pd2)->prevline || (*pd1)->prevline == (*pd2)->nextline )) line = (*pd1)->prevline; else if ( (*pd1)->nextline != NULL && ( (*pd1)->nextline == (*pd2)->prevline || (*pd1)->nextline == (*pd2)->nextline )) line = (*pd1)->nextline; + else if ( (*pd2)->prevline != NULL && + ( (*pd2)->prevline == (*pd1)->prevline || (*pd2)->prevline == (*pd1)->nextline )) + line = (*pd2)->prevline; + else if ( (*pd2)->nextline != NULL && + ( (*pd2)->nextline == (*pd1)->prevline || (*pd2)->nextline == (*pd1)->nextline )) + line = (*pd2)->nextline; else return( 0 ); @@ -2575,11 +2585,16 @@ static int stem_cmp( const void *_p1, const void *_p2 ) { struct stemdata * const *st1 = _p1, * const *st2 = _p2; double start1, end1, start2, end2; + /* Sort each stem along an axis determined by that stem's own + * orientation, so that the comparison is symmetric */ if ( fabs( (*st1)->unit.x ) > fabs( (*st1)->unit.y )) { start1 = (*st1)->right.y; end1 = (*st1)->left.y; - start2 = (*st2)->right.y; end2 = (*st2)->left.y; } else { start1 = (*st1)->left.x; end1 = (*st1)->right.x; + } + if ( fabs( (*st2)->unit.x ) > fabs( (*st2)->unit.y )) { + start2 = (*st2)->right.y; end2 = (*st2)->left.y; + } else { start2 = (*st2)->left.x; end2 = (*st2)->right.x; } -- 2.39.5